/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/graphics/dx11.1/details/common.hpp>
#include <baja/graphics/dx11.1/graphics_fwd.hpp>
#include <baja/graphics/dx11.1/details/enum_mappings.hpp>
#include <baja/graphics/dx11.1/details/util.hpp>

namespace baja { namespace graphics { namespace dx11_1 {

namespace d = baja::graphics::dx11_1::details;

class dynamic_vertex_buffer : public idynamic_vertex_buffer
{
public:
    dynamic_vertex_buffer();

    static std::shared_ptr<dynamic_vertex_buffer> create(
            const std::shared_ptr<iengine>& graphics,
            const std::shared_ptr<ivertex_declaration>& vertexDecl,
            const int vertexSize,
            const int totalVertices
            );

    // idynamic_vertex_buffer 

    void begin(
        const std::shared_ptr<iengine>& graphics,
        const primitive_type primitive
        );

    void addVertices(
        const std::shared_ptr<iengine>& graphics,
        const void* vertexData,
        const uint32 vertexSize,
        const uint32 vertexCount
        );

    void end(
        const std::shared_ptr<iengine>& graphics
        );

    void reset();

private:
    void initialize(
        const std::shared_ptr<iengine>& graphics,
        const std::shared_ptr<ivertex_declaration>& vertexDecl,
        const uint32 vertexSize,
        const uint32 totalVertices
        );

    uint32 computePrimitiveCount();

    void flush(const std::shared_ptr<iengine>& graphics);
    void flushAndContinue(const std::shared_ptr<iengine>& graphics);

    uint32 getVertsToAddAgain();

private:

    std::shared_ptr<ivertex_buffer> _vertexBuffer;
    std::shared_ptr<ivertex_declaration> _vertexDecl;

    memory* _vbData;

    primitive_type _primitive;

    uint32 _maxVerts;
    uint32 _vertexSize;
    uint32 _startVert;
    uint32 _currentVert;

    bool _active;
    bool _valid;
};

// implementation
//
inline dynamic_vertex_buffer::dynamic_vertex_buffer()
{
    // init members
    _vbData = nullptr;
    _primitive = primitive_type::triangleList;
    _maxVerts = 0;
    _vertexSize = 0;
    _startVert = 0;
    _currentVert = 0;
    _active = false;
    _valid = false;
}

// initializes the object
inline void dynamic_vertex_buffer::initialize(
    const std::shared_ptr<iengine>& graphics,
    const std::shared_ptr<ivertex_declaration>& vertexDecl,
    const uint32 vertexSize,
    const uint32 totalVertices
    )
{
    BAJA_CHECK_ARG(vertexSize != 0);
    BAJA_CHECK_ARG(totalVertices != 0);

    win32_result wr;

    if (_valid) wr = E_FAIL; // ERROR: 

    // create the vertex buffer
    _vertexBuffer = graphics->createVertexBuffer(
        vertexSize * totalVertices,
        vertex_buffer_usage::dynamic,
        graphics
        );

    // initialize rest of members 
    _vertexDecl = vertexDecl;
    _vbData = nullptr;
    _primitive = primitive_type::triangleList;
    _maxVerts = totalVertices;
    _vertexSize = vertexSize;
    _startVert = 0;
    _currentVert = 0;
    _active = false;
    _valid = true;
}

// begin to write into the buffer the specified type of primitive data
inline void dynamic_vertex_buffer::begin(const std::shared_ptr<iengine>& graphics, const primitive_type primitive)
{
    BAJA_CHECK_ARG(graphics);
    
    win32_result wr;

    if (!_valid) wr = E_FAIL; // ERROR:
    if (_active) wr = E_FAIL; // ERROR:

    // can we lock the buffer at current location, or need to discard it?
    if ((_maxVerts - _startVert) >= 3)
    {
        // still have room in the VB, no need to discard entire VB yet
        _vertexBuffer->lockNoOverwrite(
            _startVert * _vertexSize,
            (_maxVerts - _startVert) * _vertexSize,
            &_vbData);
    }
    else
    {
        // not enough room in VB so discard entire contents and start at beginning
        _vertexBuffer->lockDiscard(&_vbData);
        _startVert = 0;
    }

    // remember type of primitive we're going to render
    _primitive = primitive;

    // current vertex is always zero-based from the starting vertex
    _currentVert = 0;

    // set active flag
    _active = true;
}

// adds a vertex to the buffer for the current type of primitive data
inline void dynamic_vertex_buffer::addVertices(
    const std::shared_ptr<iengine>& graphics,
    const void* vertexData,
    const uint32 vertexSize,
    const uint32 vertexCount
    )
{
    BAJA_CHECK_ARG(graphics);
    BAJA_CHECK_ARG(vertexData);
    BAJA_CHECK_ARG(vertexSize == _vertexSize);
    BAJA_CHECK_ARG(vertexCount > 0);

    memory* dataToCopy = (memory*)vertexData;

    win32_result wr;

    if (!_valid) wr = E_FAIL; // ERROR:
    if (!_active) wr = E_FAIL; // ERROR:
    
    uint32 count = vertexCount;

    // add the vertices
    while (count != 0)
    {
        // flush as needed
        BAJA_VERIFY(_startVert + _currentVert <= _maxVerts);
        if (_startVert + _currentVert == _maxVerts)
        {
            this->flushAndContinue(graphics);
        }

        // determine how much we can copy
        uint32 remainingSpace = _maxVerts - (_startVert + _currentVert);
        uint32 vertsToCopy = min(count, remainingSpace);

        // copy
        if (vertsToCopy > 0)
        {
            std::memcpy(_vbData + (_currentVert * _vertexSize), dataToCopy, vertsToCopy * _vertexSize);
            _currentVert += vertsToCopy;
            dataToCopy += vertsToCopy * _vertexSize;
            count -= vertsToCopy;
        }
    }
}

// ends writing into the buffer
inline void dynamic_vertex_buffer::end(const std::shared_ptr<iengine>& graphics)
{
    win32_result wr;

    if (!_valid) wr = E_FAIL; // ERROR:
    if (!_active) wr = E_FAIL; // ERROR:
    
    // flush vertex buffer, which will unlock the VB and render any primitives
    this->flush(graphics);

    // adjust starting vertex index
    _startVert += _currentVert;
    BAJA_VERIFY(_startVert <= _maxVerts);
    _currentVert = 0;

    // no longer active
    _active = false;
}

// resets the buffer, unlocking and losing all data previously written to it
inline void dynamic_vertex_buffer::reset()
{
    if (_active)
    {
        // unlock the VB, effectively losing any data written since Begin() was last called
        _vertexBuffer->unlock();

        // reset members
        _vbData = nullptr;
        _currentVert = 0;
        _active = false;
    }
}

// computes the amount of primitives we can draw
inline uint32 dynamic_vertex_buffer::computePrimitiveCount()
{
    uint32 primitiveCount = 0;

    switch (_primitive)
    {
        case primitive_type::pointList:

            primitiveCount = _currentVert;
            break;

        case primitive_type::lineList:

            primitiveCount = _currentVert / 2;
            break;

        case primitive_type::triangleList:

            primitiveCount = _currentVert / 3;
            break;

        case primitive_type::lineStrip:

            primitiveCount = (_currentVert >= 2 ? _currentVert - 1 : 0);
            break;

        case primitive_type::triangleStrip:

            primitiveCount = (_currentVert >=3 ? _currentVert - 2 : 0);
            break;
    }

    return primitiveCount;
}

// flush the vertex buffer and render primitives
inline void dynamic_vertex_buffer::flush(const std::shared_ptr<iengine>& graphics)
{
    if (_active)
    {
        // unlock the vertex buffer
        _vertexBuffer->unlock();
        _vbData = nullptr;

        // determine how many primitives we can draw
        uint32 primitiveCount = this->computePrimitiveCount();

        // do we have primitives to draw?
        if (primitiveCount > 0)
        {
            // set the vertex decl
            graphics->setVertexDeclaration(_vertexDecl);

            // set the vertex stream
            graphics->setStreamSource(
                0, 
                _vertexBuffer, 
                0,
                _vertexSize);

            // draw the primitives
            graphics->drawPrimitive(
                _primitive,
                _startVert,
                primitiveCount);
        }
    }
}

// flushes the vertex buffer (which causes a render or occur) and continues by re-locking the 
// buffer again, beginning at the start
inline void dynamic_vertex_buffer::flushAndContinue(const std::shared_ptr<iengine>& graphics)
{
    BAJA_VERIFY(_startVert + _currentVert == _maxVerts);

    // allocate cache for vertex data we may need to copy back into VB after the flush
    std::vector<memory> vertCacheVector;
    vertCacheVector.resize(_vertexSize * 2);
    memory* vertCache = &(vertCacheVector[0]);

    // determine how many previously written vertices we need to copy back into the VB
    uint32 numToAdd = this->getVertsToAddAgain();
    if (numToAdd != 0)
    {
        BAJA_VERIFY(numToAdd <= 2);
        uint32 vbOffset = (_currentVert - numToAdd) * _vertexSize;
        CopyMemory(vertCache, _vbData + vbOffset, numToAdd * _vertexSize);
    }

    // flush the buffer, which causes the primitives to render
    this->flush(graphics);

    // lock VB again, this time discarding the contents of the buffer
    _vertexBuffer->lockDiscard(&_vbData);

    // reset members to write at start of vertex buffer
    _startVert = 0;
    _currentVert = 0;

    // if we have any cached vertices to re-add, do so now
    if (numToAdd != 0)
    {
        CopyMemory(_vbData, vertCache, numToAdd * _vertexSize);
        _currentVert = numToAdd;
    }
}

// Gets the vertices we need to re-add back into the buffer once it is flushed
inline uint32 dynamic_vertex_buffer::getVertsToAddAgain()
{
    uint32 numToAdd = 0;

    switch (_primitive)
    {
        case primitive_type::lineList:

            numToAdd = _currentVert % 2;
            break;

        case primitive_type::triangleList:

            numToAdd = _currentVert % 3;
            break;

        case primitive_type::lineStrip:

            numToAdd = (_currentVert > 0 ? 1 : 0);
            break;

        case primitive_type::triangleStrip:

            numToAdd = (_currentVert > 1 ? 2 : _currentVert);
            break;
    }

    return numToAdd;
}

inline std::shared_ptr<dynamic_vertex_buffer> dynamic_vertex_buffer::create(
        const std::shared_ptr<iengine>& graphics,
        const std::shared_ptr<ivertex_declaration>& vertexDecl,
        const int vertexSize,
        const int totalVertices
        )
{
    std::shared_ptr<dynamic_vertex_buffer> returnValue = std::make_shared<dynamic_vertex_buffer>();
    returnValue->initialize(
            graphics,
            vertexDecl,
            vertexSize,
            totalVertices
            );

    return returnValue;
}

}}} // namespace baja.graphics.dx11_1

namespace baja { namespace graphics {

    // creates an Idynamic_vertex_buffer
inline std::shared_ptr<idynamic_vertex_buffer>
createDynamicVertexBuffer(
    const std::shared_ptr<iengine>& graphics,
    const std::shared_ptr<ivertex_declaration>& vertexDecl,
    const uint32 vertexSize,
    const uint32 totalVertices
    )
{
    BAJA_CHECK_ARG(graphics);
    BAJA_CHECK_ARG(vertexDecl);

    std::shared_ptr<baja::graphics::dx11_1::dynamic_vertex_buffer> returnValue = 
        baja::graphics::dx11_1::dynamic_vertex_buffer::create(graphics, vertexDecl, vertexSize, totalVertices);
    return std::dynamic_pointer_cast<idynamic_vertex_buffer>(returnValue);
}

}} // namespace baja.graphics